//!
/************************************************************************************************************
 * @file RBMemoryManager.cpp
 ************************************************************************************************************/

/************************************************************************************************************
 * Precompiled Header
 ************************************************************************************************************/
#include "RBMainPCH.h"
 
/************************************************************************************************************
 * Includes
 ************************************************************************************************************/

#include "RBMemoryManager.h"


/************************************************************************************************************
 * Defines
 ************************************************************************************************************/

/************************************************************************************************************
 * Implementation
 ************************************************************************************************************/

#if RB_USE_MEMORY_MANAGER
// Pragma black Magic to be able to be the 1st / last object to be created (because of static variables that may do heap allocations)
// like static maps
#pragma warning(disable:4074) // warning C4074: initializers put in compiler reserved initialization area
#pragma init_seg(compiler)
RBMemoryTracker RBMemoryManager::ms_Tracker;

struct RBMemHeader
{
    uint32 ID;
    size_t AllocatedSize;
    const char* File;
    int Line;
    const char* Function;
    RBMemoryManager::EAllocType AllocType;

    RBMemHeader* Prev;
    RBMemHeader* Next;

    static RBMemHeader* ms_pFirst;
    static RBMemHeader* ms_pLast;
};

RBMemHeader* RBMemHeader::ms_pFirst = 0;
RBMemHeader* RBMemHeader::ms_pLast = 0;


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
static inline void AddMemHeader(RBMemHeader* pHeader)
{
    pHeader->Next = 0;
    pHeader->Prev = RBMemHeader::ms_pLast;
    if (!RBMemHeader::ms_pFirst) RBMemHeader::ms_pFirst = pHeader;
    if (RBMemHeader::ms_pLast) RBMemHeader::ms_pLast->Next = pHeader;
    RBMemHeader::ms_pLast = pHeader;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
static inline void RemoveMemHeader(RBMemHeader* pHeader)
{
    if (pHeader->Prev) pHeader->Prev->Next = pHeader->Next;
    else               RBMemHeader::ms_pFirst = RBMemHeader::ms_pFirst->Next;

    if (pHeader->Next) pHeader->Next->Prev = pHeader->Prev;
    else               RBMemHeader::ms_pLast = RBMemHeader::ms_pLast->Prev;
}

bool   RBMemoryManager::TrackMemory = false;
uint32 RBMemoryManager::ms_AllocatedObjects = 0;
uint32 RBMemoryManager::ms_uiAllocID = 0;

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void* RBMemoryManager::Allocate(size_t stSize, RBMemoryManager::EAllocType eAllocType)
{
    // Deal with NULL allocation
    if (stSize == 0)
    {
        stSize = 1;
    }

    void* pRawBuffer = NULL;
    if (TrackMemory)
    {
        pRawBuffer = malloc(sizeof(RBMemHeader) + stSize);
        RBMemHeader* pHeader = (RBMemHeader*) pRawBuffer;
        pHeader->ID = ms_uiAllocID++;
        pHeader->AllocatedSize = stSize;
        pHeader->File = ms_Tracker.File;
        pHeader->Line = ms_Tracker.Line;
        pHeader->Function = ms_Tracker.Function;
        pHeader->AllocType = eAllocType;
        AddMemHeader(pHeader);
        pRawBuffer = (((char*) pRawBuffer) + sizeof(RBMemHeader));
        ms_Tracker.TrackMemory(pRawBuffer);
        ++ms_AllocatedObjects;
    }
    else
    {
        pRawBuffer = malloc(stSize);
    }

    return pRawBuffer;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBMemoryManager::Deallocate(void* pAddress, RBMemoryManager::EAllocType eAllocType)
{
    // This parameter is not used in release
    UNUSED(eAllocType);
    if (pAddress)
    {
        if (TrackMemory)
        {
            bool bIsMemoryTracked = ms_Tracker.IsMemoryTracked(pAddress, true);
            if (bIsMemoryTracked)
            {
                char* pcBuffer = (char*) pAddress;
                RBMemHeader* pHeader = (RBMemHeader*) ((void*)(pcBuffer - sizeof(RBMemHeader)));

                RBAssert(pHeader->AllocType == eAllocType, 
                    RBStringTools::FormatString("Warning: Trying to delete an object with a different allocation policy in %s (%s, line %d)",
                    ms_Tracker.File, ms_Tracker.Function, ms_Tracker.Line).c_str());


                --ms_AllocatedObjects;
                RemoveMemHeader(pHeader);
                pAddress = pHeader;
            }
        }
        free(pAddress);
    }
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBMemoryManager::HasMemLeaks()
{
    return ms_AllocatedObjects != 0;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBString RBMemoryManager::GetLeaksDescription()
{
    RBString desc;
    desc += RBStringTools::FormatString("-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
    desc += RBStringTools::FormatString("                                                        File( Line)|     Size|      ID\n");
    desc += RBStringTools::FormatString("-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
    RBMemHeader* pHeader = RBMemHeader::ms_pFirst;
    while (pHeader)
    {
        RBString strShortFileName = RBStringTools::GetLeafName(pHeader->File);
        desc += RBStringTools::FormatString("% 60s(% 5d)|% 9d|% 8d\n\n", pHeader->File, pHeader->Line, pHeader->AllocatedSize, pHeader->ID);
        //desc += RBStringTools::FormatString("% 8d|% 12d||% 8d|% 60s|% 14d\n", pHeader->ID, pHeader->AllocatedSize, strShortFileName.c_str(), pHeader->Line, pHeader->Function, pHeader->AllocType);
        desc += RBStringTools::FormatString("-----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
        pHeader = pHeader->Next;
    }
    return desc;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBMemoryManager::SetOwner(const char* pcFile, int iLine, const char* pcFunc)
{
    ms_Tracker.File = pcFile;
    ms_Tracker.Line = iLine;
    ms_Tracker.Function = pcFunc;
}
/************************************************************************************************************/
#endif
